﻿using System;
using System.Net.Sockets;
using System.Net;
using System.Collections;
using System.Collections.Generic;

namespace TcpConnection
{
    public class TcpServer
    {
        private Socket _socketListen = null;
        private IPEndPoint _endpoint = null;
        private Dictionary<EndPoint , Socket> _dctClients = null;
        private object _lckDct = new object();

        public EventHandler<ClientConnectionChangedEventArgs> NewClinetConnectedEventHandler = null;
        public EventHandler<ClientDataReceivedEventArgs> ClientDataReceivedEventHandler = null;
        public EventHandler<ClientConnectionChangedEventArgs> ClientDisconnedtedEventHandler = null;
        public EndPoint LocalEndPoint
        {
            get
            {
                return _socketListen.LocalEndPoint;
            }
        }
        public int ClientCount
        {
            get
            {
                lock(_lckDct)
                {
                    return _dctClients.Count;
                }
            }
        }

        public TcpServer(IPAddress ip , int port)
        {
            _endpoint = new IPEndPoint(ip ,port);
            _dctClients = new Dictionary<EndPoint , Socket>();
        }

        public bool StartListen(int backlog = 100)
        {
            try 
            {
                _socketListen = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp);
                _socketListen.Bind(_endpoint);
                _socketListen.Listen(backlog);
                StartAccept();
                return true;
            }
            catch(Exception)
            {
                return false;
            }
        }

        public void StopListen()
        {
            _socketListen.Close();
            lock(_lckDct)
            {
                foreach(var pair in _dctClients)
                {
                    pair.Value.Disconnect(false);
                    pair.Value.Close();
                    _dctClients.Remove(pair.Key);
                }
            }
        }

        private void StartAccept()
        {
            Socket client = null;
            try 
            {
                _socketListen.BeginAccept(rst =>
                {
                    try 
                    {
                        client = _socketListen.EndAccept(rst);
                        _dctClients.Add(client.RemoteEndPoint , client);
                        if(NewClinetConnectedEventHandler != null)
                        {
                            NewClinetConnectedEventHandler.Invoke(this , new ClientConnectionChangedEventArgs(client));
                        }
                        StartReceive(client);
                        StartAccept();
                    }
                    catch(SocketException)
                    {
                        DisconnedtClient(client);
                    }
                    catch(Exception)
                    {
                        throw;
                    }
                } , null);
            }
            catch(SocketException)
            {
                DisconnedtClient(client);
            }
            catch(Exception)
            {
                throw;
            }
        }

        private void StartReceive(Socket socket)
        {
            try 
            {
                byte[] buffer = new byte[1024];
                socket.BeginReceive(buffer, 0 , buffer.Length , SocketFlags.None , rst =>
                {
                    try
                    {
                        int len = socket.EndReceive(rst);
                        if (len == 0)
                        {
                            DisconnedtClient(socket);
                            return;
                        }
                        byte[] temp = new byte[len];
                        Array.Copy(buffer, temp, len);
                        if (ClientDataReceivedEventHandler != null)
                        {
                            ClientDataReceivedEventHandler.Invoke(this, new ClientDataReceivedEventArgs(temp, socket));
                        }
                        StartReceive(socket);
                    }
                    catch (SocketException)
                    {
                        DisconnedtClient(socket);
                    }
                    catch(Exception)
                    {
                        throw;
                    }
                } , null);
            }
            catch(SocketException)
            {
                DisconnedtClient(socket);
            }
            catch(Exception)
            {
                throw;
            }
        }

        private void DisconnedtClient(Socket client)
        {
            try
            {
                if(client != null)
                {
                    if(client.Connected)
                    {
                        client.Disconnect(false);
                    }
                    if (ClientDisconnedtedEventHandler != null)
                    {
                        ClientDisconnedtedEventHandler.Invoke(this , new ClientConnectionChangedEventArgs(client));
                    }
                    RemoveClient(client);
                    client.Dispose();
                    client = null;
                }
            }
            catch
            {
                throw;
            }
        }

        private void AddClient(Socket client)
        {
            try
            {
                if(client == null)
                    return;
                if(!client.Connected)  
                    return;
                lock(_lckDct)
                {
                    _dctClients.Add(client.RemoteEndPoint , client);
                }
            }
            catch
            {
                throw;
            }
        }

        private void RemoveClient(Socket client)
        {
            try
            {
                if(client == null)
                    return;
                lock(_lckDct)
                {
                    if(_dctClients.ContainsKey(client.RemoteEndPoint))
                        _dctClients.Remove(client.RemoteEndPoint);
                }
            }
            catch(Exception)
            {
                throw;
            }
        }
    }
}
